/* * (C) Copyright 2015 by fr3ts0n <erwin.scheuch-heilig@gmx.at> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package com.fr3ts0n.ecu.gui.androbd; import android.app.ActionBar; import android.app.Activity; import android.graphics.Color; import android.graphics.Paint.Align; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.view.Menu; import android.view.MenuItem; import android.view.WindowManager; import android.widget.ListAdapter; import com.fr3ts0n.ecu.EcuDataPv; import com.fr3ts0n.ecu.prot.obd.ObdProt; import org.achartengine.ChartFactory; import org.achartengine.GraphicalView; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.BasicStroke; import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer; import java.util.Timer; import java.util.TimerTask; import java.util.TreeSet; /** * <code>Activity</code> that displays the readout of one <code>Sensor</code>. * This <code>Activity</code> must be started with an <code>Intent</code> that * passes in the number of the <code>Sensor</code>(s) to display. If none is * passed, the first available <code>Sensor</code> is used. */ public class ChartActivity extends Activity { /** * minimum time between screen updates */ public static final long MIN_UPDATE_TIME = 1000; /** * For passing the index number of the <code>Sensor</code> in its * <code>SensorManager</code> */ public static final String POSITIONS = "POSITIONS"; /** Map to uniquely collect PID numbers */ private TreeSet<Integer> pidNumbers = new TreeSet<Integer>(); /** * List of colors to be used for series */ public static final int[] colors = { Color.LTGRAY, Color.DKGRAY, Color.RED, Color.BLUE, Color.GREEN, Color.GRAY, Color.CYAN, Color.parseColor("#FF000080"), // navy Color.YELLOW, Color.parseColor("#FF00FFFF"), // aqua Color.parseColor("#FFFF00FF"), // fuchsia Color.parseColor("#FF800000"), // maroon Color.parseColor("#FF00FF00"), // lime Color.MAGENTA, Color.parseColor("#FF808000"), // olive Color.parseColor("#FF800080"), // purple Color.parseColor("#FFC0C0C0"), // silver Color.parseColor("#FF008080"), // teal }; /** * list of colors to be used for series */ public static final BasicStroke stroke[] = { BasicStroke.SOLID, BasicStroke.DASHED, BasicStroke.DOTTED, }; /** * The displaying component */ private GraphicalView chartView; /** * Dataset of the graphing component */ private XYMultipleSeriesDataset sensorData; /** * Renderer for actually drawing the graph */ private XYMultipleSeriesRenderer renderer; /** automatic hiding toolbar */ private AutoHider toolBarHider; /** * the wake lock to keep app communication alive */ private static WakeLock wakeLock; private static ListAdapter mAdapter = null; /** data adapter as source of display data */ public static ListAdapter getAdapter() { return mAdapter; } public static void setAdapter(ListAdapter mAdapter) { ChartActivity.mAdapter = mAdapter; } /** * get color for an ID number preferrably unique pid number * this is to get persistent coloring/lining for each id * @param id id to get color for * @return color for given ID */ public static int getItemColor(int id) { return colors[id % colors.length]; } /** * get stroke for an ID number preferrably unique pid number * this is to get persistent coloring/lining for each id * @param id id to get color for * @return color for given ID */ public static BasicStroke getStroke(int id) { return stroke[(id / colors.length) % stroke.length]; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.chart, menu); return true; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // keep main display on? if(MainActivity.prefs.getBoolean("keep_screen_on", false)) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } // prevent activity from falling asleep PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getString(R.string.app_name)); wakeLock.acquire(); // set up action bar ActionBar actionBar = getActionBar(); if (actionBar != null) { actionBar.setDisplayShowTitleEnabled(true); } setTitle(R.string.chart); /* get PIDs to be shown */ int positions[] = getIntent().getIntArrayExtra(POSITIONS); // set up overall chart properties sensorData = new XYMultipleSeriesDataset(); renderer = new XYMultipleSeriesRenderer(positions.length); chartView = ChartFactory.getTimeChartView(this, sensorData, renderer, "H:mm:ss"); // set up global renderer renderer.setXTitle(getString(R.string.time)); renderer.setXLabels(5); renderer.setYLabels(5); renderer.setGridColor(Color.DKGRAY); renderer.setShowGrid(true); renderer.setFitLegend(true); renderer.setClickEnabled(false); // set up chart data setUpChartData(positions); // make chart visible setContentView(chartView); // limit selected PIDs to selection MainActivity.setFixedPids(pidNumbers); // if auto hiding selected ... if(MainActivity.prefs.getBoolean(MainActivity.PREF_AUTOHIDE,false)) { // get autohide timeout [s] int timeout = Integer.valueOf( MainActivity.prefs.getString(MainActivity.PREF_AUTOHIDE_DELAY,"15") ); // auto hide toolbar toolBarHider = new AutoHider( this, mHandler, MainActivity.MESSAGE_TOOLBAR_VISIBLE, timeout * 1000); toolBarHider.start(1000); // wake up on touch chartView.setOnTouchListener(toolBarHider); } } /** * Handle message requests */ private transient final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MainActivity.MESSAGE_UPDATE_VIEW: /* update chart */ chartView.invalidate(); break; // set toolbar visibility case MainActivity.MESSAGE_TOOLBAR_VISIBLE: Boolean visible = (Boolean)msg.obj; // set action bar visibility ActionBar ab = getActionBar(); if(ab != null) { if(visible) { ab.show(); } else { ab.hide(); } } break; } } }; /** * Handle menu selections * * @param item selected menu item * @return result of super call */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.share: new ExportTask(this).execute(sensorData); break; case R.id.snapshot: Screenshot.takeScreenShot(this, getWindow().peekDecorView()); break; } return super.onOptionsItemSelected(item); } /** * Handle destroy of the Activity */ @Override protected void onDestroy() { if(toolBarHider != null) { // cancel hiding thread toolBarHider.cancel(); // forget about it toolBarHider = null; } ObdProt.resetFixedPid(); // allow sleeping again wakeLock.release(); super.onDestroy(); } Timer refreshTimer = new Timer(); /** * Timer Task to cyclically update data screen */ private TimerTask updateTask = new TimerTask() { @Override public void run() { /* forward message to update the view */ Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_UPDATE_VIEW); mHandler.sendMessage(msg); } }; /* (non-Javadoc) * @see android.app.Activity#onStart() */ @Override protected void onStart() { super.onStart(); // start display update task try { refreshTimer.schedule(updateTask, 0, 1000); } catch (Exception e) { // exception ignored here ... } } /* (non-Javadoc) * @see android.app.Activity#onStop() */ @Override protected void onStop() { refreshTimer.purge(); super.onStop(); } /** * Set up all the charting data series * * @param positions Positions of PIDs withn adapter */ private void setUpChartData(int[] positions) { long startTime = System.currentTimeMillis(); int i = 0; EcuDataPv currPv; XYSeries currSeries; pidNumbers.clear(); // loop through all PIDs for (int position : positions) { // get corresponding Process variable currPv = (EcuDataPv) mAdapter.getItem(position); if (currPv == null) continue; int pid = currPv.getAsInt(EcuDataPv.FID_PID); // add PID to unique list of PIDs pidNumbers.add(pid); // get contained data series currSeries = (XYSeries) currPv.get(ObdItemAdapter.FID_DATA_SERIES); if (currSeries == null) continue; // add initial measurement to series data to ensure // at least one measurement is available if (currSeries.getItemCount() < 1) currSeries.add(startTime, (Float) currPv.get(EcuDataPv.FID_VALUE)); // set scale to display series currSeries.setScaleNumber(i); // register series to graph sensorData.addSeries(i, currSeries); /* set up series visual parameters */ renderer.setYTitle(String.valueOf(currPv.get(EcuDataPv.FID_UNITS)), i); renderer.setYAxisAlign(((i % 2) == 0) ? Align.LEFT : Align.RIGHT, i); renderer.setYLabelsAlign(((i % 2) == 0) ? Align.LEFT : Align.RIGHT, i); renderer.setYLabelsColor(i, getItemColor(pid)); /* set up new line renderer */ XYSeriesRenderer r = new XYSeriesRenderer(); r.setColor(getItemColor(pid)); r.setStroke(getStroke(pid)); // register line renderer renderer.addSeriesRenderer(i, r); i++; } } }